package scope

import (
	"reflect"

	"gorm.io/gorm"
)

func WithModel(model any) Scope {
	return func(db *gorm.DB) *gorm.DB {
		return db.Model(model)
	}
}

func Where(where any, args ...any) IScope {
	return WhereRule{
		Where: where,
		Args:  args,
	}
}

type WhereRule struct {
	Where any
	Args  []any
}

func (w WhereRule) MakeScope() Scope {
	return w.handleOrm
}

func (w WhereRule) handleOrm(db *gorm.DB) *gorm.DB {
	return db.Where(w.Where, w.Args...)
}

type WhereRules []WhereRule

func (w WhereRules) MakeScope() Scope {
	return func(db *gorm.DB) *gorm.DB {
		for _, r := range w {
			db = r.handleOrm(db)
		}
		return db
	}
}

func WithPKIn(inValues ...any) IScope {
	return InRule{
		UsePk:    true,
		InValues: inValues,
	}
}
func WithIn(column string, inValues ...any) IScope {
	return InRule{
		Column:   column,
		UsePk:    false,
		InValues: inValues,
	}
}

type InRule struct {
	Column   string
	UsePk    bool
	InValues []any
}

// MakeScope implements IScope.
func (i InRule) MakeScope() Scope {
	return i.handleOrm
}

func (i InRule) getValues() []any {
	if len(i.InValues) == 0 {
		return nil
	}

	vals := make([]any, 0)
	for _, v := range i.InValues {
		inV := reflect.ValueOf(v)
		switch inV.Kind() {
		case reflect.Slice, reflect.Array:
			for j := 0; j < inV.Len(); j++ {
				vals = append(vals, inV.Index(j).Interface())
			}
		default:
			vals = append(vals, v)
		}
	}
	return vals
}

func (i InRule) handleOrm(db *gorm.DB) *gorm.DB {
	if len(i.InValues) == 0 {
		return db
	}
	vals := i.getValues()
	if i.Column != "" {
		return db.Where(i.Column+" IN (?)", vals)
	} else if i.UsePk &&
		db.Statement != nil &&
		db.Statement.Schema != nil &&
		len(db.Statement.Schema.PrimaryFieldDBNames) > 0 {
		for _, pk := range db.Statement.Schema.PrimaryFieldDBNames {
			if len(vals) > 1 {
				return db.Where(pk+" IN (?)", vals)
			} else {
				return db.Where(pk+" = ?", vals[0])
			}
		}
	}
	return db
}
